package com.study;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import java.util.zip.ZipOutputStream;

@SuppressWarnings("ResultOfMethodCallIgnored")
public class ZipStreamUtil
{
    /**
     * 压缩目录
     *
     * @param dir  待压缩的目录
     * @param path 当前目录在压缩包里的路径，从压缩根目录开始的路径
     * @param zos  关联压了缩包的压缩流
     */
    public static void dirToZip(File dir, String path, ZipOutputStream zos) throws IOException
    {
        //1.进入src文件夹
        File[] files = dir.listFiles();
        //2.遍历数组
        assert files != null;
        for (File file : files) {
            String filePath = path + "\\" + file.getName();
            if (file.isFile()) {
                //3.判断-文件，变成ZipEntry对象，放入到压缩包当中
                ZipEntry entry = new ZipEntry(filePath);
                zos.putNextEntry(entry);
                //读取文件中的数据，写到压缩包
                fileToZos(file, zos);
                zos.closeEntry();
            } else {
                //4.判断-文件夹，递归
                dirToZip(file, filePath, zos);
            }
        }
    }

    /**
     * 压缩目录
     * 把文件夹的 entry 也压缩到包里
     * 压缩包里会缺少根目录的 entry，解压时如果不先创建根目录，可能会有问题
     *
     * @param dir  待压缩的目录
     * @param path 当前目录在压缩包里的路径，从压缩根目录开始的路径
     * @param zos  关联压了缩包的压缩流
     */
    public static void dirToZip1(File dir, File path, ZipOutputStream zos) throws IOException
    {
        File[] files = dir.listFiles();
        assert files != null;
        for (File file : files) {
            // 如果 file 是文件夹，拼接上父路径的新路径是下次递归的父路径
            File filePath = new File(path, file.getName());
            fileToZip(file, filePath.getPath(), zos);

            // 如果遍历到的 file 对象是目录，则进行递归
            if (file.isDirectory()) {
                dirToZip1(file, filePath, zos);
            }
        }
    }

    /**
     * 压缩目录
     * 把根目录的 entry 也压缩到包里
     *
     * @param file 文件夹下的文件或者文件夹的 File 对象
     * @param path 当前 file 在压缩包里的路径，从压缩根目录开始的路径
     *             只使用 File 类型对象的 path 路径字符串
     * @param zos  关联压了缩包的压缩流
     */
    public static void dirToZip2(File file, File path, ZipOutputStream zos) throws IOException
    {
        fileToZip(file, path.getPath(), zos);

        // 如果方法传入的 file 对象是文件夹，则对文件夹里的每个 file 对象进行递归
        if (file.isDirectory()) {
            File[] files = file.listFiles();
            assert files != null;
            for (File f : files) {
                File filePath = new File(path, f.getName());
                dirToZip2(f, filePath, zos);
            }
        }
    }

    /**
     * 压缩目录
     * 去掉 path 参数
     *
     * @param file 文件夹下的文件或者文件夹的 File 对象
     * @param zos  关联压了缩包的压缩流
     */
    public static void dirToZip3(File file, ZipOutputStream zos) throws IOException
    {
        String path = file.getPath();
        if (path.startsWith(TEST_DIR)) {
            path = path.substring(TEST_DIR.length());
        }
        fileToZip(file, path, zos);

        // 如果方法传入的 file 对象是文件夹，则对文件夹里的每个 file 对象进行递归
        if (file.isDirectory()) {
            File[] files = file.listFiles();
            assert files != null;
            for (File f : files) {
                dirToZip3(f, zos);
            }
        }
    }

    /**
     * 文件写入压缩输出流
     *
     * @param file          磁盘上文件的 File 对象
     * @param filePathInZip file 在压缩包中的路径，file 的路径是相对当前项目在磁盘上的路径
     * @param zos           压缩输出流
     */
    public static void fileToZip(File file, String filePathInZip, ZipOutputStream zos) throws IOException
    {
        // 1、根据文件全限定名创建 ZipEntry 对象，放入压缩输出流
        ZipEntry entry = file.isDirectory() ?
                new ZipEntry(filePathInZip + "/") :
                new ZipEntry(filePathInZip);
        zos.putNextEntry(entry);

        // 2、如果是文件，则把文件写入压缩输出流
        if (file.isFile()) {
            fileToZos(file, zos);
        }
        // 3、关闭一个 entry 的写入
        zos.closeEntry();
    }

    /**
     * 压缩目录
     * 还是只要把文件的 entry 放进压缩包就可以，在解压时，根据文件路径的父路径创建出它所需要的目录
     *
     * @param dir 待压缩的目录
     * @param zos 关联压了缩包的压缩流
     */
    public static void dirToZip4(File dir, ZipOutputStream zos) throws IOException
    {
        File[] files = dir.listFiles();
        assert files != null;
        for (File file : files) {
            if (file.isFile()) {
                String pathInZip = file.getPath();
                if (pathInZip.startsWith(TEST_DIR)) {
                    pathInZip = pathInZip.substring(TEST_DIR.length());
                }
                ZipEntry entry = new ZipEntry(pathInZip);
                zos.putNextEntry(entry);
                fileToZos(file, zos);
                zos.closeEntry();
            } else {
                dirToZip4(file, zos);
            }
        }
    }
    /**
     * 解压压缩包
     * 解压没有目录 entry 的压缩包，根据文件路径的父路径创建出它所需要的目录
     *
     * @param zipFile  压缩包
     * @param unzipDir 解压目录
     */
    public static void unzip4(File zipFile, File unzipDir) throws IOException
    {
        ZipInputStream zis = new ZipInputStream(new FileInputStream(zipFile));
        try (zis) {
            ZipEntry entry;
            while ((entry = zis.getNextEntry()) != null) {
                System.out.println(entry);
                File file = new File(unzipDir, entry.toString());
                File parentFile = file.getParentFile();
                if (!parentFile.exists()) {
                    // 压缩包里不包含压缩文件的父目录的 entry，所以解压文件的父目录不存在，则先创建父目录
                    parentFile.mkdir();
                }
                zisToFile(zis, file);
                zis.closeEntry();
            }
        }
    }

    /**
     * 解压压缩包
     * 解压有目录 entry 的压缩包
     *
     * @param zipFile  压缩包
     * @param unzipDir 解压目录
     */
    public static void unzip(File zipFile, File unzipDir) throws IOException
    {
        ZipInputStream zip = new ZipInputStream(new FileInputStream(zipFile));
        try (zip) {
            ZipEntry entry;
            while ((entry = zip.getNextEntry()) != null) {
                System.out.println(entry);
                File file = new File(unzipDir, entry.toString());
                if (entry.isDirectory()) {
                    file.mkdirs();
                } else {
                    zisToFile(zip, file);
                    zip.closeEntry();
                }
            }
        }
    }

    public static void printEntry(File zipFile) throws IOException
    {
        ZipInputStream zip = new ZipInputStream(new FileInputStream(zipFile));
        try (zip) {
            ZipEntry entry;
            while ((entry = zip.getNextEntry()) != null) {
                System.out.println(entry);
            }
        }
    }

    /**
     * 文件压缩，文件写入压缩输出流
     *
     * @param file 要压缩的文件
     * @param zos  关联了压缩包的输出流
     */
    private static void fileToZos(File file, ZipOutputStream zos) throws IOException
    {
        FileInputStream fis = new FileInputStream(file);
        try (fis) {
            int b;
            while ((b = fis.read()) != -1) {
                zos.write(b);
            }
        }
    }

    /**
     * 文件解压，压缩输入流写入文件
     *
     * @param zis  关联了压缩包的输入流
     * @param file 写入的文件
     */
    private static void zisToFile(ZipInputStream zis, File file) throws IOException
    {
        FileOutputStream fos = new FileOutputStream(file);
        try (fos) {
            int b;
            while ((b = zis.read()) != -1) {
                //写到目的地
                fos.write(b);
            }
        }
    }

    static String TEST_DIR = String.format("%s\\%s", "src\\main\\resources", "zip_stream");
}
